﻿using Google.Protobuf;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using UnityEngine;

class UserSession
{
    MessageDispatcher m_Dispatcher = new MessageDispatcher();
    //byte[] m_SendBuffer = new byte[1024];
    //byte[] m_RecvBuffer;
    MemoryStream m_SendStream;
    MemoryStream m_RecvStream;
    public IPEndPoint RemoteIP { private set; get; }
    public uint UserID { private set; get; }
    public Vector3 Pos
    {
        get
        {
            return m_Player.m_Pos;
        }
    }
    public float RotY
    {
        get
        {
            return m_Player.m_RotY;
        }
    }
    public uint PlayerID
    {
        get
        {
            return m_Player.m_PlayerID;
        }
    }

    public uint TickCount
    {
        get
        {
            return ServerApp.Instance.m_TickCount;
        }
    }

    Player __Player;
    public Player m_Player
    {
        private set
        {
            __Player = value;
            LogManager.Log("m_Player set id=" + UserID + ", value.id=" + (value == null ? 0 : value.m_PlayerID));
        }
        get
        {
            LogManager.Log("m_Player get id=" + UserID + ", __Player.id" + (__Player == null ? 0 : __Player.m_PlayerID));
            return __Player;
        }
    }

    Queue<byte[]> m_RecvMsgs = new Queue<byte[]>();
    bool m_WaitingStop;
    MessageManager m_MessageManager = new MessageManager();
    byte[] m_ChanSync;
    byte[] m_ChanHello;
    byte[] m_ChanJoin;
    byte[] m_ChanPing;
    public bool IsConnect { private set; get; }
    UDPReliableTracking m_Tracking = new UDPReliableTracking();
    BinaryWriter m_BinaryWriter;
    BinaryReader m_BinaryReader;
    List<UDPPacket> m_ResentList = new List<UDPPacket>();
    float m_LastPing;
    const float MAX_GAP_PING = 5f;
    UDPPacket m_PingPacket = new UDPPacket() { m_Cmd = (ushort)EMessageType.PING, m_Data = new byte[0] };
    uint m_ResendCount = 0; 
    
    public UserSession(uint userId, IPEndPoint ip)
    {
        m_SendStream = new MemoryStream();
        //m_RecvBuffer = new byte[1024];
        m_BinaryWriter = new BinaryWriter(m_SendStream);
        m_RecvStream = new MemoryStream();
        m_BinaryReader = new BinaryReader(m_RecvStream);

        RemoteIP = ip;
        UserID = userId;
        LogManager.Log("create new userid=" + userId);
        m_Dispatcher.Register<Ping>(EMessageType.PING, (msg) =>
        {
            m_LastPing = MessageManager.Instance.GetCurElapsedTime();
            //LogManager.Error(string.Format("{0} 更新Ping time={1}", UserID, m_LastPing));
        });
        m_Dispatcher.Register<Ack>(EMessageType.ACK, (msg) =>
        {
            if (!IsConnect)
            {
                // 收到第一个ACK代表连接成功
                // 第一个ACK是Hello的ACK
                IsConnect = true;
                ServerApp.Instance.SendAIJoin(UserID);
            }
            else
            {
                LogManager.Error(string.Format("{0} 接收ACK cmd={1},seqId={2}, ackbits={3}, totalLength={4}", UserID, EMessageType.ACK, msg.AckSeqId, msg.AckBits, 0));
                m_Tracking.RecvAck(msg);
            }
        });
        m_Dispatcher.Register<Hello>(EMessageType.HELLO, (msg) =>
        {
            var m = m_MessageManager.Get<Hello>();
            Send(new Hello() { UserId = UserID}, UserID, true);
        });
        m_Dispatcher.Register<C2S_PlayerJoin>(EMessageType.C2S_JOIN, (msg) =>
        {
            m_Player = new Player();
            m_Player.Init(this, ServerApp.Instance.m_PlayerIDGen.GetUniqueID(), ServerApp.Instance.GetRandomPos(5), RotY);

            // response. 
            S2C_PlayerJoin m = new S2C_PlayerJoin();
            m.UserId = UserID;
            m.PlayerId = m_Player.m_PlayerID;
            m.NickName = "玩家" + UserID;
            m.PosX = Pos.x;
            m.PosY = Pos.y;
            m.PosZ = Pos.z;
            m.RotY = RotY;
            m.TickCount = TickCount;
            Send(m);
            ServerApp.Instance.SendPlayerJoinBatch(UserID);
            
            LogManager.Log("S2C_PlayerJoin userid=" + UserID + ", pos=" + Pos + ", recv tick=" + ServerApp.Instance.m_TickCount);
        });
        m_Dispatcher.Register<C2S_PlayerSync>(EMessageType.C2S_SYNC, (msg) =>
        {
            if (m_Player == null)
            {
                return;
            }
            m_Player.UpdatePos(new Vector3(msg.PosX, msg.PosY, msg.PosZ), msg.RotY);
            LogManager.Log("C2S_SYNC userid=" + UserID + ", pos=" + Pos + ", recv tick=" + ServerApp.Instance.m_TickCount);
        });
        m_Dispatcher.Register<C2S_OrderTest>(EMessageType.C2S_ORDER_TEST, (msg) =>
        {
            LogManager.Error("C2S_ORDER_TEST content=" + msg.Content);
            for (int i = 0; i < 3; i++)
            {
                S2C_OrderTest m = new S2C_OrderTest();
                m.Content = msg.Content + i.ToString();
                Send(m, UserID, true);
            }
        });

        Task.Run(KeepAliveThread);
    }

    public void KeepAliveThread()
    {
        while (true)
        {
            if (m_WaitingStop)
            {
                return;
            }
            Send(m_PingPacket);
            Thread.Sleep((int)(MAX_GAP_PING * 1000) / 2);
        }
    }

    public void Stop()
    {
        m_Dispatcher.ClearAll();
        m_WaitingStop = true;
    }

    public void RecvProcess()
    {
        while (m_RecvMsgs.Count > 0)
        {
            ProcessMessage(m_RecvMsgs.Dequeue());
            LogManager.Log("userid=" + UserID + ", user session recv process m_RecvMsgs.Count=" + m_RecvMsgs.Count);
        }
    }


    public void Update()
    {
        if (m_WaitingStop)
        {
            return;
        }

        if (m_LastPing > 0 && MessageManager.Instance.GetCurElapsedTime() > m_LastPing + MAX_GAP_PING)
        {
            LogManager.Error("Ping超时 gap=" + (MessageManager.Instance.GetCurElapsedTime() - m_LastPing));
            ServerApp.Instance.RemoveUser(RemoteIP);
            return;
        }

        // TODO 
        RecvProcess();

        if (m_ChanSync != null)
        {
            var m = ProcessChanMessage(m_ChanSync);
            m_ChanSync = null;
            //LogManager.Log("userid=" + UserID + ", Update process sync tickcount=" + (m as C2S_PlayerSync).TickCount);
            m_Dispatcher.Invoke(EMessageType.C2S_SYNC, m);
        }
        if (m_ChanHello != null)
        {
            var m = ProcessChanMessage(m_ChanHello);
            m_ChanHello = null;
            LogManager.Log("userid=" + UserID + ", Update process hello");
            m_Dispatcher.Invoke(EMessageType.HELLO, m);
        }
        if (m_ChanJoin != null)
        {
            var m = ProcessChanMessage(m_ChanJoin);
            m_ChanJoin = null;
            LogManager.Log("userid=" + UserID + ", Update process join");
            m_Dispatcher.Invoke(EMessageType.C2S_JOIN, m);
        }
        if (m_ChanPing != null)
        {
            var m = ProcessChanMessage(m_ChanPing);
            m_ChanPing = null;
            LogManager.Log("userid=" + UserID + ", Update process ping");
            m_Dispatcher.Invoke(EMessageType.PING, m);
        }

        m_Tracking.Update(m_ResentList);
    }

    public void Tick()
    {
        if (m_WaitingStop)
        {
            return;
        }
        var m = m_MessageManager.Get<S2C_PlayerSync>();
        m.Players.Clear();
        UserSession u;
        m.TickCount = TickCount;
        for (int i = ServerApp.Instance.m_Users.Count - 1; i >= 0; i--)
        {
            u = ServerApp.Instance.m_Users[i];
            var p = m_MessageManager.Get<S2C_PlayerSyncSingle>();
            p.PlayerId = u.PlayerID;
            p.PosX = u.Pos.x;
            p.PosY = u.Pos.y;
            p.PosZ = u.Pos.z;
            p.RotY = u.RotY;
            m.Players.Add(p);
        }
        LogManager.Log("userid=" + UserID + ", send sync tick=" + m.TickCount);
        //Send(m, UserID);
    }

    public void Send(IMessage msg, uint userId = 0, bool isReliable = false)
    {
        UDPPacket packet = new UDPPacket();
        var cmd = m_MessageManager.GetMessageType(msg);
        packet.m_IsReliable = isReliable;
        packet.m_Cmd = (ushort)cmd;
        packet.m_Data = msg.ToByteArray();
        Send(packet, userId);
    }

    public void Send(UDPPacket packet, uint userId = 0)
    {
        if (packet == null)
        {
            return;
        }

        if (packet.m_IsReliable)
        {
            m_Tracking.ApplyReliableInfo(packet);
        }
        else
        {
            //m_MessageManager.Put(packet.);
        }

        m_SendStream.Seek(0, SeekOrigin.Begin);
        m_BinaryWriter.Flush();
        m_BinaryWriter.Write(packet.m_Cmd);
        m_BinaryWriter.Write(packet.m_IsReliable);
        m_BinaryWriter.Write(packet.m_SeqId);
        m_BinaryWriter.Write(packet.m_OrderId);
        m_BinaryWriter.Write(packet.m_Data);
        var bs = new byte[m_SendStream.Position];
        Buffer.BlockCopy(m_SendStream.GetBuffer(), 0, bs, 0, bs.Length);
        ServerApp.Instance.Send(bs, userId);

        string s = "";
        //foreach (var item in bs)
        //{
        //    s += item + ", ";
        //}
        Console.WriteLine(string.Format("{0} 发送 cmd={1},seqId={2}, orderId={3}, totalLength={4}, s={5}", UserID, (EMessageType)packet.m_Cmd, packet.m_SeqId, packet.m_OrderId, bs.Length, s));
        LogManager.Log(string.Format("{0} 发送 cmd={1},seqId={2}, orderId={3}, totalLength={4}", UserID, (EMessageType)packet.m_Cmd, packet.m_SeqId, packet.m_OrderId, bs.Length));
    }

    public void RecvMessage(Queue<byte[]> bs)
    {
        if (bs == null || bs.Count == 0) { return; }
        LogManager.Log("userid=" + UserID + ", RecvMessage all count=" + bs.Count);
        foreach (var item in bs)
        {
            m_RecvMsgs.Enqueue(item);
        }
    }

    public void ProcessMessage(byte[] bs)
    {
        if (bs == null)
        {
            return;
        }

        var cmd = (EMessageType)BitConverter.ToUInt16(bs, 0);
        //LogManager.Log("receive cmd=" + cmd);
        if (cmd == EMessageType.C2S_SYNC)
        {
            m_ChanSync = bs;
        }
        else if (cmd == EMessageType.HELLO)
        {
            m_ChanHello = bs;
        }
        else if (cmd == EMessageType.C2S_JOIN)
        {
            m_ChanJoin = bs;
        }
        else if (cmd == EMessageType.PING)
        {
            m_ChanPing = bs;
        }
        else
        {
            var m = ProcessChanMessage(bs);
            m_Dispatcher.Invoke(cmd, m);
        }
    }

    public IMessage ProcessChanMessage(byte[] bs)
    {
        if (bs == null)
        {
            return null;
        }

        UDPPacket packet = new UDPPacket();
        m_RecvStream.Seek(0, SeekOrigin.Begin);
        m_RecvStream.Write(bs, 0, bs.Length);
        long lastPosition = m_RecvStream.Position;
        m_RecvStream.Seek(0, SeekOrigin.Begin);
        packet.m_Cmd = m_BinaryReader.ReadUInt16();
        packet.m_IsReliable = m_BinaryReader.ReadBoolean();
        packet.m_SeqId = m_BinaryReader.ReadUInt16();
        packet.m_OrderId = m_BinaryReader.ReadUInt16();
        packet.m_Data = m_BinaryReader.ReadBytes((int)(lastPosition - m_RecvStream.Position));

        string s = "";
        //foreach (var item in bs)
        //{
        //    s += item + ", ";
        //}
        //Console.WriteLine(string.Format("{0} 接收 cmd={1},seqId={2}, orderId={3}, totalLength={4}, s={5}", UserID, (EMessageType)packet.m_Cmd, packet.m_SeqId, packet.m_OrderId, bs.Length, s));
        LogManager.Log(string.Format("{0} 接收 cmd={1},seqId={2}, orderId={3}, totalLength={4}", UserID, ((EMessageType)packet.m_Cmd), packet.m_SeqId, packet.m_OrderId, bs.Length));

        var cmd = (EMessageType)packet.m_Cmd;
        LogManager.Log("userid=" + UserID + ", ProcessChanMessage cmd=" + cmd);
        var type = m_MessageManager.GetMessageSystemType(cmd);
        var m = m_MessageManager.Get(type);
        m.MergeFrom(packet.m_Data);

        if (packet.m_IsReliable)
        {
            LogManager.Error(string.Format("{0} 发送ACK cmd={1},seqId={2}, ackBits={3}, totalLength={4}", UserID, (EMessageType)packet.m_Cmd, packet.m_SeqId, m_Tracking.GenAckBits(packet.m_SeqId), bs.Length));

            m_Tracking.AckSent(packet.m_SeqId);

            if (cmd == EMessageType.C2S_RESEND_TEST)
            {
                m_ResendCount++;
                if (m_ResendCount == 3)
                {
                    m_ResendCount = 0;
                    Send(new Ack() { AckSeqId = packet.m_SeqId, AckBits = m_Tracking.GenAckBits(packet.m_SeqId) }, UserID);

                    S2C_ResendTest res = new S2C_ResendTest();
                    res.Content = (m as C2S_ResendTest).Content;
                    Send(res, UserID, false);
                }
            }
            else
            {
                Send(new Ack() { AckSeqId = packet.m_SeqId, AckBits = m_Tracking.GenAckBits(packet.m_SeqId) }, UserID);
            }
        }
        return m;
    }
}
